//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14

// <any>

// Check that we're consistently using std::allocator_traits to
// allocate/deallocate/construct/destroy objects in std::any.
// See https://llvm.org/PR45099 for details.

#include <any>
#include <cassert>
#include <cstddef>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>

#include "test_macros.h"


// Make sure we don't fit in std::any's SBO
struct Large { char big[sizeof(std::any) + 1]; };

// Make sure we fit in std::any's SBO
struct Small { };

bool Large_was_allocated = false;
bool Large_was_constructed = false;
bool Large_was_destroyed = false;
bool Large_was_deallocated = false;

bool Small_was_constructed = false;
bool Small_was_destroyed = false;

template <>
struct std::allocator<Large> {
  using value_type                             = Large;
  using size_type                              = std::size_t;
  using difference_type                        = std::ptrdiff_t;
  using propagate_on_container_move_assignment = std::true_type;
  using is_always_equal                        = std::true_type;

  Large* allocate(std::size_t n) {
    Large_was_allocated = true;
    return static_cast<Large*>(::operator new(n * sizeof(Large)));
  }

  template <typename... Args>
  void construct(Large* p, Args&&... args) {
    new (p) Large(std::forward<Args>(args)...);
    Large_was_constructed = true;
  }

  void destroy(Large* p) {
    p->~Large();
    Large_was_destroyed = true;
  }

  void deallocate(Large* p, std::size_t) {
    Large_was_deallocated = true;
    return ::operator delete(p);
  }
};

template <>
struct std::allocator<Small> {
  using value_type                             = Small;
  using size_type                              = std::size_t;
  using difference_type                        = std::ptrdiff_t;
  using propagate_on_container_move_assignment = std::true_type;
  using is_always_equal                        = std::true_type;

  Small* allocate(std::size_t) {
    assert(false);
    return nullptr;
  }

  template <typename... Args>
  void construct(Small* p, Args&&... args) {
    new (p) Small(std::forward<Args>(args)...);
    Small_was_constructed = true;
  }

  void destroy(Small* p) {
    p->~Small();
    Small_was_destroyed = true;
  }

  void deallocate(Small*, std::size_t) { assert(false); }
};

int main(int, char**) {
  // Test large types
  {
    {
      std::any a = Large();
      (void)a;

      assert(Large_was_allocated);
      assert(Large_was_constructed);
    }

    assert(Large_was_destroyed);
    assert(Large_was_deallocated);
  }

  // Test small types
  {
    {
      std::any a = Small();
      (void)a;

      assert(Small_was_constructed);
    }

    assert(Small_was_destroyed);
  }

  return 0;
}
